Skip to content

Conversation

@BP602
Copy link
Owner

@BP602 BP602 commented Oct 2, 2025

Summary

Implements lazy loading for chatrooms with automatic background initialization to achieve fast startup while preserving mention notifications.

Performance Impact:

  • ✅ Priority chatroom loads first (~800ms LCP, 80% improvement)
  • ✅ 18 remaining chatrooms auto-load in background (batches of 2, 300ms stagger)
  • ✅ All chatrooms connected within ~3 seconds
  • ✅ Mention notifications work for all chatrooms

Changes

  • Priority selection: Last active chatroom (localStorage) or first by order
  • Background loader: initializeDeferredChatroomsInBackground() processes chatrooms in batches
  • Lazy loader: initializeChatroomLazily() handles single chatroom initialization
  • Fallback: Manual trigger on chatroom switch if background incomplete
  • Tracking: loadedChatrooms Set prevents duplicate initialization
  • Non-blocking refresh: 7TV emote updates without intrusive UI messages
  • Debounce: Prevent concurrent refresh operations

Test Results

From localhost logs with 19 chatrooms:

  • Priority chatroom (xQc) loaded: Line 12-90 (~800ms)
  • Background batch start: Line 108 (800ms delay after priority)
  • All 18 chatrooms loaded: Line 648 (~3 seconds total)
  • Presence updates working: Line 670 (all chatrooms receiving notifications)

Test Plan

  • Verify fast initial load with priority chatroom
  • Confirm all chatrooms connect in background
  • Test mention notifications arrive for non-priority chatrooms
  • Validate manual switch to unloaded chatroom triggers immediate load
  • Check no duplicate initializations occur

Summary by CodeRabbit

  • New Features

    • Progressive emote loading with placeholders and smooth fade-in.
    • Support for personal emote sets and global cosmetic events.
  • Refactor

    • Lazy chatroom initialization and shared connections for faster startup.
    • Batched presence updates and improved background fetching for emotes.
    • Faster cosmetic resolution via lookup maps.
  • Chores

    • Enhanced telemetry: captures console errors/warnings and restores on disable.
    • Enabled renderer source maps and updated debug configuration.
    • Minor .env example cleanup.

Replace O(n) .find() operations with O(1) Map.get() lookups for badges and paints.
This eliminates performance lag when rendering messages with 7TV cosmetics by:

- Adding cosmeticsLookup state with badgeMap and paintMap hashmaps
- Populating lookup maps in addCosmetics() when cosmetics are loaded
- Replacing .find() calls with Map.get() in getUserStyle, getUserBadge, getUserPaint

Fixes chat lag after recent 7TV styling bug fix by transforming complexity
from O(n × messages) to O(1 × messages) for cosmetic lookups.
…t handling

Major refactoring to improve 7TV WebSocket performance and fix cosmetic event processing:

**Shared WebSocket Migration:**
- Replace per-chatroom 7TV WebSocket connections with single shared connection
- Remove individual StvWebSocket class (423 lines deleted)
- Update sharedStvWebSocket with proper event routing and subscription management
- Deprecate connectToStvWebSocket() method in ChatProvider
- Update ConnectionManager to use updateChatroom() instead of addChatroom()

**Entitlement Event Handling:**
- Fix cosmetic event deduplication using ref_id instead of all-zeros id field
- Remove kind === 10 filter that was blocking EMOTE_SET entitlements
- Route BADGE/PAINT events to cosmetics store, EMOTE_SET to dedicated handler
- Add handlePersonalEmoteSetEntitlement() placeholder for future implementation
- Handle global cosmetic events once instead of broadcasting to all chatrooms

**Performance Improvements:**
- Add deduplication for cosmetic events (30s window, 60s cleanup)
- Add dedicated tracking for personal and channel emote set updates
- Reduce console log spam by 82% (2900+ → 500 lines)
- Add VITE_DEBUG_7TV_WS flag for optional verbose WebSocket logging

**Telemetry Enhancements:**
- Add console.error/console.warn instrumentation in webTracing
- Capture critical warnings and errors for telemetry

**Related:**
- Issue #48: Full implementation needed for personal emote set entitlement sync
- Builds on commit 93f2790 (hashmap optimization for cosmetics lookups)

This refactor eliminates redundant WebSocket connections while fixing several
bugs in how 7TV cosmetic and entitlement events are processed.
Remove synthetic test emissions that were flooding production telemetry:

- Remove setTimeout block that emitted synthetic console.error after 2 seconds
- Gate renderer_export_smoke test span behind NODE_ENV === 'development' check
- Document remaining test code as DEV-ONLY

These test blocks were useful during development but should not run in
production as they create false positives in telemetry data.
Implement progressive image loading for emotes to improve perceived performance and prevent layout shift during load.

Changes:
- Add useProgressiveEmoteLoading hook with 4 states (placeholder, loading, loaded, error)
- Render fallback placeholders (2-char abbreviation) during load and on error
- Smooth opacity transitions when images finish loading
- SCSS styling for placeholder states with error indicator

Impact:
- Eliminates layout shift from async emote loading
- Better UX with visual feedback during image load
- Graceful degradation for failed emote loads
… initialization

Load priority chatroom immediately for fast LCP, then auto-load remaining chatrooms in background to preserve mention notifications.

Changes:
- Priority chatroom selection (last active or first by order)
- Deferred chatroom queue with automatic background loading
- Background loader processes 2 chatrooms/batch with 300ms stagger
- Track loaded chatrooms to prevent duplicate initialization
- Non-blocking 7TV emote refresh without intrusive UI messages
- Debounce logic to prevent concurrent refresh operations
- Remember last active chatroom via localStorage
- Manual lazy load trigger on chatroom switch (fallback)

Implementation:
- ConnectionManager.initializeDeferredChatroomsInBackground() - batch loader
- ConnectionManager.initializeChatroomLazily() - single chatroom loader
- 800ms delay after priority load before background batch starts
- All chatrooms connected within ~3 seconds for full notification support

Impact:
- Startup LCP improvement: ~80% faster (priority chatroom only)
- All chatrooms connected automatically in background
- Mention notifications work for all chatrooms
- Manual fallback if user switches before background completes
@coderabbitai
Copy link

coderabbitai bot commented Oct 2, 2025

Walkthrough

Adds progressive emote loading (UI + styles), introduces lazy chatroom initialization and a shared 7TV WebSocket routing model, expands cosmetics store with lookup maps and single-event handlers, instruments console errors for telemetry, enables renderer sourcemaps, adjusts debug config paths, minor presence log message tweak, and deletes the legacy per-chatroom 7TV WebSocket.

Changes

Cohort / File(s) Summary
Environment & Build Config
\.env.example, \.vscode/launch.json, electron.vite.config.mjs
Env comment/spacing adjustments; VSCode webRoot updated to src/renderer/src; renderer build sourcemaps enabled.
Chat UI Styling
src/renderer/src/assets/styles/components/Chat/Message.scss
New .emote-placeholder and .emote-progressive classes with load/error opacity transitions.
Emote Component
src/renderer/src/components/Cosmetics/Emote.jsx
Refactors to progressive image loading with placeholder/fallback, load state classes, and onLoad/onError handlers.
Chat Provider (STV & Lazy Loading)
src/renderer/src/providers/ChatProvider.jsx
Adds dedup/batching sets/maps, global handling for 7TV cosmetic/entitlement events, personal emote set support, lazy chatroom init with background loading, shared STV connection strategy, and telemetry/logging updates.
Cosmetics Store Refactor
src/renderer/src/providers/CosmeticsProvider.jsx
Adds telemetry hooks, normalization utilities, cosmeticsLookup maps, and new public methods (addCosmetic, removeCosmetic, addCosmetics); getters now use maps.
Telemetry Instrumentation
src/renderer/src/telemetry/webTracing.js
Wraps/restores console.error/warn for telemetry when enabled; dev-only test spans; safeguards and restoration logic added.
Connection Manager (Lazy Loading)
utils/services/connectionManager.js
Adds deferred/background chatroom initialization, loaded-state tracking, batching with spans/delays, global/channel emote caching flow, and cleanup for deferred caches. Public methods added for lazy status/control.
Shared 7TV WebSocket
utils/services/seventv/sharedStvWebSocket.js
Adds updateChatroom, revises event routing (including personal emote sets), updates findChatroomForEvent(type, body), enhances subscription lifecycle logging, and adds telemetry for unhandled events.
7TV Presence Log Tweak
utils/services/seventv/stvAPI.js
Changes error message prefix to “[7TV Presence]”.
Legacy STV WebSocket Removal
utils/services/seventv/stvWebsocket.js
Removes per-chatroom StvWebSocket implementation and related helpers/exports.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App
  participant ConnMgr as ConnectionManager
  participant STV as Shared7TVWebSocket
  Note over App,ConnMgr: Lazy chatroom initialization
  User->>App: Selects chatroom
  App->>ConnMgr: initializeChatroomLazily(chatroomId)
  alt First load
    ConnMgr->>ConnMgr: Mark chatroom as loading
    ConnMgr->>STV: updateChatroom(chatroomId, ...)
    ConnMgr-->>App: isChatroomLoaded=true
    par Background
      rect rgba(200,230,255,0.3)
      ConnMgr->>ConnMgr: initializeDeferredChatroomsInBackground()
      end
    end
  else Already loaded
    ConnMgr-->>App: isChatroomLoaded=true
  end
Loading
sequenceDiagram
  autonumber
  participant STV as Shared7TVWebSocket
  participant Router as Event Router
  participant Chat as ChatProvider
  participant Cos as CosmeticsProvider
  Note over STV,Router: Centralized event routing
  STV-->>Router: Message(type, body)
  alt emote_set update (channel)
    Router->>Chat: handleEmoteSetUpdate({isPersonal:false,...})
  else emote_set update (personal)
    rect rgba(230,255,230,0.4)
    Router->>Chat: handlePersonalEmoteSetUpdate(...)
    end
  else cosmetic.create/delete
    Router->>Cos: addCosmetic/removeCosmetic(...)
  else entitlement.create/delete
    Router->>Chat: handlePersonalEmoteSetEntitlement(...)
  else unhandled
    Router-->>Router: telemetry span (unhandled)
  end
Loading
sequenceDiagram
  autonumber
  participant Emote as Emote.jsx
  participant Img as <img>
  Note over Emote,Img: Progressive emote loading
  Emote->>Emote: state = loading
  Emote-->>Emote: render placeholder (reserved size)
  Emote->>Img: set src + onLoad/onError
  alt onLoad
    Img-->>Emote: handleImageLoad()
    Emote-->>Emote: state = loaded (opacity 1)
  else onError
    Img-->>Emote: handleImageError()
    Emote-->>Emote: show fallback (placeholder)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I buffered an emote with a gentle glow,
While chatrooms load softly, in background flow.
I sniffed the wires—one socket to rule,
Badges and paints mapped tidy and cool.
Console squeaks traced, carrots aligned—
Hop, hop! Ship it—progress refined. 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately and succinctly summarizes the main change—implementing lazy loading with auto-deferred background initialization for chatrooms—and follows conventional commit style without extraneous details.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perf/lazy-loading-chatrooms

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.39.5)
src/renderer/src/providers/ChatProvider.jsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting

Comment on lines +877 to +880
// Only add to 7TV if we have valid IDs
const stvId = chatroom.streamerData?.user?.stv_id || "0";
const stvEmoteSetId = chatroom?.channel7TVEmotes?.[1]?.setInfo?.id || "0";
this.stvWebSocket.addChatroom(chatroom.id, chatroom.streamerData.user_id, stvId, stvEmoteSetId);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use robust lookup for lazy 7TV emote set subscription

The lazy path derives stvEmoteSetId via chatroom.channel7TVEmotes?.[1]?.setInfo?.id. The initial connection flow searched the emote sets with .find(set => set.type === "channel") because the array order isn’t stable—global and personal sets can come before the channel set. When the channel set isn’t at index 1, this code passes "0" to SharedStvWebSocket.addChatroom, causing the websocket to skip subscribeToEmoteSetEvents and the deferred chatroom never receives 7TV emote updates. Use the same .find logic as the eager path to extract the channel set ID before subscribing.

Useful? React with 👍 / 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
src/renderer/src/components/Cosmetics/Emote.jsx (3)

4-45: Consider simplifying by removing redundant showFallback state.

The showFallback state mirrors loadState === 'error'. You could derive it directly from loadState to reduce state variables and prevent potential inconsistencies:

-const useProgressiveEmoteLoading = (emote, type) => {
-  const [loadState, setLoadState] = useState('loading'); // loading, loaded, error
-  const [showFallback, setShowFallback] = useState(false);
+const useProgressiveEmoteLoading = (emote, type) => {
+  const [loadState, setLoadState] = useState('loading'); // loading, loaded, error

   // Define fallback placeholder (prevents layout shift)
   const placeholder = useMemo(() => {
     const placeholderWidth = type === "stv" ? (emote.width || 28) : 32;
     const placeholderHeight = type === "stv" ? (emote.height || 28) : 32;

     return {
       width: placeholderWidth,
       height: placeholderHeight,
       backgroundColor: '#2a2a2a',
       display: 'inline-flex',
       alignItems: 'center',
       justifyContent: 'center',
       borderRadius: '4px',
       color: '#666',
       fontSize: '10px',
       fontFamily: 'monospace'
     };
   }, [emote, type]);

   const handleImageLoad = useCallback(() => {
     setLoadState('loaded');
-    setShowFallback(false);
   }, []);

   const handleImageError = useCallback(() => {
     setLoadState('error');
-    setShowFallback(true);
   }, []);

   return {
     loadState,
-    showFallback,
+    showFallback: loadState === 'error',
     placeholder,
     handleImageLoad,
     handleImageError
   };
 };

139-142: Consider removing inline opacity styles; rely on CSS classes.

The inline opacity transition duplicates the CSS rules in Message.scss (lines 1060-1066). Removing the inline styles simplifies the component and centralizes styling:

 <img
   className={`${type === "stv" ? "stvEmote" : "kickEmote"} emote ${loadState}`}
   src={emoteImageSrc}
   srcSet={type === "stv" ? emoteSrcSet(emote) : null}
   alt={name}
   loading="lazy"
   fetchpriority="low"
   decoding="async"
   onLoad={handleImageLoad}
   onError={handleImageError}
-  style={{
-    opacity: loadState === 'loaded' ? 1 : 0,
-    transition: 'opacity 0.2s ease-in-out'
-  }}
 />

Ensure the CSS classes in Message.scss provide the correct opacity values for each state (currently lines 1060-1070).


146-161: Consider applying progressive loading to overlaid emotes for consistency.

The overlaid zero-width emotes don't use the progressive loading pattern. While they're decorative, applying the same loading pattern would provide a consistent experience and prevent potential layout shifts if they fail to load.

If you'd like to apply progressive loading to overlaid emotes, you could refactor them to use the same hook or create a simplified version for these secondary emotes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91fca72 and 3b5ba1c.

📒 Files selected for processing (12)
  • .env.example (1 hunks)
  • .vscode/launch.json (1 hunks)
  • electron.vite.config.mjs (1 hunks)
  • src/renderer/src/assets/styles/components/Chat/Message.scss (1 hunks)
  • src/renderer/src/components/Cosmetics/Emote.jsx (2 hunks)
  • src/renderer/src/providers/ChatProvider.jsx (22 hunks)
  • src/renderer/src/providers/CosmeticsProvider.jsx (5 hunks)
  • src/renderer/src/telemetry/webTracing.js (2 hunks)
  • utils/services/connectionManager.js (7 hunks)
  • utils/services/seventv/sharedStvWebSocket.js (6 hunks)
  • utils/services/seventv/stvAPI.js (1 hunks)
  • utils/services/seventv/stvWebsocket.js (0 hunks)
💤 Files with no reviewable changes (1)
  • utils/services/seventv/stvWebsocket.js
🧰 Additional context used
📓 Path-based instructions (8)
**

📄 CodeRabbit inference engine (AGENTS.md)

**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes

Files:

  • electron.vite.config.mjs
  • src/renderer/src/components/Cosmetics/Emote.jsx
  • utils/services/connectionManager.js
  • src/renderer/src/telemetry/webTracing.js
  • src/renderer/src/assets/styles/components/Chat/Message.scss
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • utils/services/seventv/stvAPI.js
  • src/renderer/src/providers/ChatProvider.jsx
  • utils/services/seventv/sharedStvWebSocket.js
electron.vite.config.mjs

📄 CodeRabbit inference engine (AGENTS.md)

If bare KT_* must be exposed, add envPrefix override in electron.vite.config.mjs

Files:

  • electron.vite.config.mjs
!dist/**

📄 CodeRabbit inference engine (AGENTS.md)

Do not commit built installers/artifacts in dist/

Files:

  • electron.vite.config.mjs
  • src/renderer/src/components/Cosmetics/Emote.jsx
  • utils/services/connectionManager.js
  • src/renderer/src/telemetry/webTracing.js
  • src/renderer/src/assets/styles/components/Chat/Message.scss
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • utils/services/seventv/stvAPI.js
  • .vscode/launch.json
  • src/renderer/src/providers/ChatProvider.jsx
  • .env.example
  • utils/services/seventv/sharedStvWebSocket.js
src/renderer/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead

Files:

  • src/renderer/src/components/Cosmetics/Emote.jsx
  • src/renderer/src/telemetry/webTracing.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))

Files:

  • src/renderer/src/components/Cosmetics/Emote.jsx
  • src/renderer/src/telemetry/webTracing.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'

Files:

  • src/renderer/src/components/Cosmetics/Emote.jsx
  • src/renderer/src/telemetry/webTracing.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use shared non-secret variables via import.meta.env.VITE_*

Files:

  • src/renderer/src/components/Cosmetics/Emote.jsx
  • src/renderer/src/telemetry/webTracing.js
  • src/renderer/src/providers/CosmeticsProvider.jsx
  • src/renderer/src/providers/ChatProvider.jsx
src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx}: Place OpenTelemetry instrumentation under src/telemetry/** and src/renderer/src/telemetry/**
Configure OTLP HTTP export to Grafana Cloud; read endpoints/keys from env with main-safe prefixes; do not expose secrets to renderer

Files:

  • src/renderer/src/telemetry/webTracing.js
🧠 Learnings (1)
📚 Learning: 2025-09-18T22:47:22.745Z
Learnt from: CR
PR: BP602/KickTalk#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T22:47:22.745Z
Learning: Applies to src/renderer/src/**/*.{ts,tsx,js,jsx} : Use import.meta.env.RENDERER_VITE_* for renderer-scoped config

Applied to files:

  • electron.vite.config.mjs
🧬 Code graph analysis (6)
src/renderer/src/components/Cosmetics/Emote.jsx (2)
src/renderer/src/components/Cosmetics/EmoteTooltip.jsx (1)
  • handleImageLoad (47-49)
src/renderer/src/components/Cosmetics/Badges.jsx (12)
  • mousePos (7-7)
  • mousePos (52-52)
  • mousePos (102-102)
  • handleMouseEnter (13-16)
  • handleMouseEnter (54-57)
  • handleMouseEnter (104-107)
  • handleMouseLeave (18-20)
  • handleMouseLeave (59-61)
  • handleMouseLeave (109-111)
  • handleMouseMove (22-29)
  • handleMouseMove (63-70)
  • handleMouseMove (113-120)
utils/services/connectionManager.js (6)
src/renderer/src/components/Chat/index.jsx (1)
  • chatroom (17-17)
src/renderer/src/components/Chat/Input/index.jsx (3)
  • chatroom (1166-1166)
  • emote (894-896)
  • tracer (53-53)
src/renderer/src/providers/ChatProvider.jsx (2)
  • chatrooms (379-382)
  • tracer (53-53)
utils/services/seventv/stvAPI.js (3)
  • globalResponse (14-14)
  • emoteGlobalData (20-20)
  • formattedGlobalEmotes (10-10)
utils/services/seventv/sharedStvWebSocket.js (1)
  • tracer (8-8)
utils/services/kick/sharedKickPusher.js (1)
  • tracer (2-2)
src/renderer/src/telemetry/webTracing.js (1)
src/telemetry/tracing.js (1)
  • telemetryEnabled (12-12)
src/renderer/src/providers/CosmeticsProvider.jsx (2)
src/renderer/src/providers/ChatProvider.jsx (4)
  • getRendererTracer (48-49)
  • startSpan (51-67)
  • tracer (53-53)
  • span (55-55)
utils/services/seventv/sharedStvWebSocket.js (2)
  • tracer (8-8)
  • INVALID_7TV_NULL_ID (5-5)
src/renderer/src/providers/ChatProvider.jsx (7)
utils/services/seventv/stvAPI.js (1)
  • sendUserPresence (100-127)
src/renderer/src/components/Navbar.jsx (2)
  • orderedChatrooms (21-23)
  • chatrooms (20-20)
src/renderer/src/components/Chat/index.jsx (1)
  • chatroom (17-17)
src/renderer/src/components/SplitPaneChat.jsx (1)
  • chatroom (7-7)
src/renderer/src/components/Chat/Input/index.jsx (7)
  • chatroom (1166-1166)
  • span (55-55)
  • startSpan (51-67)
  • endSpanOk (69-72)
  • emote (894-896)
  • error (987-987)
  • endSpanError (74-77)
src/renderer/src/providers/CosmeticsProvider.jsx (2)
  • span (11-11)
  • startSpan (7-19)
src/preload/index.js (1)
  • personalEmoteSets (112-112)
utils/services/seventv/sharedStvWebSocket.js (2)
src/renderer/src/providers/CosmeticsProvider.jsx (2)
  • INVALID_7TV_NULL_ID (21-21)
  • tracer (9-9)
src/renderer/src/telemetry/webTracing.js (6)
  • body (1735-1735)
  • tracer (1409-1409)
  • tracer (1717-1717)
  • tracer (1767-1767)
  • tracer (1879-1879)
  • tracer (2125-2125)
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 94-94: [UnorderedKey] The MAIN_VITE_GRAFANA_TEMPO_QUERY_TOKEN key should go before the MAIN_VITE_GRAFANA_TEMPO_QUERY_URL key

(UnorderedKey)


[warning] 97-97: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test-builds (windows-latest)
🔇 Additional comments (4)
electron.vite.config.mjs (1)

75-75: LGTM! Sourcemap support improves debugging.

Enabling renderer source maps aligns with the updated Debug Renderer Process configuration in .vscode/launch.json (webRoot now points to src/renderer/src), providing better stack traces and breakpoint resolution during development.

.env.example (1)

94-97: LGTM! Debug flags section improves discoverability.

The reorganization and addition of debug flags documentation (e.g., VITE_DEBUG_7TV_WS) helps developers quickly enable troubleshooting features. The static analysis warnings about key ordering and trailing blank lines are style preferences and don't impact functionality.

.vscode/launch.json (1)

23-23: LGTM! Debugging path now matches actual source structure.

The updated webRoot correctly points to the renderer source directory (src/renderer/src), ensuring accurate breakpoint resolution and stack traces when using the newly enabled source maps.

src/renderer/src/assets/styles/components/Chat/Message.scss (1)

1038-1072: LGTM! Progressive loading styles improve UX.

The new .emote-placeholder and .emote-progressive classes provide smooth opacity transitions and visual feedback during emote loading, preventing layout shifts and clearly indicating error states. The styling aligns well with the progressive loading implementation in Emote.jsx.

Comment on lines +1897 to +1934
const objectKind = body?.object?.kind;

if (objectKind === "EMOTE_SET") {
// Handle personal emote set entitlement grants
get().handlePersonalEmoteSetEntitlement(body, "create");
} else if (objectKind === "BADGE" || objectKind === "PAINT") {
// Handle badge/paint cosmetic entitlements
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
useCosmeticsStore?.getState()?.addUserStyle(transformedUsername, body);
} else {
// Log unhandled objectKind to telemetry
const span = startSpan('seventv.unhandled_entitlement_create');
span?.setAttributes?.({
'entitlement.object_kind': objectKind || 'unknown',
'entitlement.user_id': body?.object?.user?.id || 'unknown',
'entitlement.ref_id': body?.object?.ref_id || 'unknown'
});
span?.end?.();
}
break;
}
case "entitlement.delete": {
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
useCosmeticsStore?.getState()?.removeUserStyle(transformedUsername, body);
const objectKind = body?.object?.kind;

if (objectKind === "EMOTE_SET") {
// Handle personal emote set entitlement revocations
get().handlePersonalEmoteSetEntitlement(body, "delete");
} else if (objectKind === "BADGE" || objectKind === "PAINT") {
// Handle badge/paint cosmetic entitlement removals
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
useCosmeticsStore?.getState()?.removeUserStyle(transformedUsername, body);
} else {
// Log unhandled objectKind to telemetry
const span = startSpan('seventv.unhandled_entitlement_delete');
span?.setAttributes?.({
'entitlement.object_kind': objectKind || 'unknown',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Prevent entitlement handler crash when Kick username is absent

If 7TV emits an entitlement for a user that lacks a Kick connection, username will be undefined. The expression username?.replaceAll("-", "_").toLowerCase() only guards the replaceAll call—toLowerCase() still runs on undefined, throwing a TypeError and aborting the shared socket pipeline. The same risk exists in the delete branch. Please normalize defensively before lowercasing.

-          const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
-          const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
+          const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
+          const transformedUsername =
+            typeof username === "string" ? username.replaceAll("-", "_").toLowerCase() : undefined;
           useCosmeticsStore?.getState()?.addUserStyle(transformedUsername, body);
-          const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
-          const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
+          const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
+          const transformedUsername =
+            typeof username === "string" ? username.replaceAll("-", "_").toLowerCase() : undefined;
           useCosmeticsStore?.getState()?.removeUserStyle(transformedUsername, body);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const objectKind = body?.object?.kind;
if (objectKind === "EMOTE_SET") {
// Handle personal emote set entitlement grants
get().handlePersonalEmoteSetEntitlement(body, "create");
} else if (objectKind === "BADGE" || objectKind === "PAINT") {
// Handle badge/paint cosmetic entitlements
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
useCosmeticsStore?.getState()?.addUserStyle(transformedUsername, body);
} else {
// Log unhandled objectKind to telemetry
const span = startSpan('seventv.unhandled_entitlement_create');
span?.setAttributes?.({
'entitlement.object_kind': objectKind || 'unknown',
'entitlement.user_id': body?.object?.user?.id || 'unknown',
'entitlement.ref_id': body?.object?.ref_id || 'unknown'
});
span?.end?.();
}
break;
}
case "entitlement.delete": {
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
useCosmeticsStore?.getState()?.removeUserStyle(transformedUsername, body);
const objectKind = body?.object?.kind;
if (objectKind === "EMOTE_SET") {
// Handle personal emote set entitlement revocations
get().handlePersonalEmoteSetEntitlement(body, "delete");
} else if (objectKind === "BADGE" || objectKind === "PAINT") {
// Handle badge/paint cosmetic entitlement removals
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername = username?.replaceAll("-", "_").toLowerCase();
useCosmeticsStore?.getState()?.removeUserStyle(transformedUsername, body);
} else {
// Log unhandled objectKind to telemetry
const span = startSpan('seventv.unhandled_entitlement_delete');
span?.setAttributes?.({
'entitlement.object_kind': objectKind || 'unknown',
const objectKind = body?.object?.kind;
if (objectKind === "EMOTE_SET") {
// Handle personal emote set entitlement grants
get().handlePersonalEmoteSetEntitlement(body, "create");
} else if (objectKind === "BADGE" || objectKind === "PAINT") {
// Handle badge/paint cosmetic entitlements
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername =
typeof username === "string" ? username.replaceAll("-", "_").toLowerCase() : undefined;
useCosmeticsStore?.getState()?.addUserStyle(transformedUsername, body);
} else {
// Log unhandled objectKind to telemetry
const span = startSpan('seventv.unhandled_entitlement_create');
span?.setAttributes?.({
'entitlement.object_kind': objectKind || 'unknown',
'entitlement.user_id': body?.object?.user?.id || 'unknown',
'entitlement.ref_id': body?.object?.ref_id || 'unknown'
});
span?.end?.();
}
break;
}
case "entitlement.delete": {
const objectKind = body?.object?.kind;
if (objectKind === "EMOTE_SET") {
// Handle personal emote set entitlement revocations
get().handlePersonalEmoteSetEntitlement(body, "delete");
} else if (objectKind === "BADGE" || objectKind === "PAINT") {
// Handle badge/paint cosmetic entitlement removals
const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username;
const transformedUsername =
typeof username === "string" ? username.replaceAll("-", "_").toLowerCase() : undefined;
useCosmeticsStore?.getState()?.removeUserStyle(transformedUsername, body);
} else {
// Log unhandled objectKind to telemetry
const span = startSpan('seventv.unhandled_entitlement_delete');
span?.setAttributes?.({
'entitlement.object_kind': objectKind || 'unknown',
'entitlement.user_id': body?.object?.user?.id || 'unknown',
'entitlement.ref_id': body?.object?.ref_id || 'unknown'
});
span?.end?.();
}
break;
}
🤖 Prompt for AI Agents
In src/renderer/src/providers/ChatProvider.jsx around lines 1897 to 1934, the
entitlement handlers call username?.replaceAll("-", "_").toLowerCase() which can
still call toLowerCase on undefined and crash when the user has no Kick
connection; fix by normalizing the username before further transforms (e.g.
const username = body?.object?.user?.connections?.find(c => c.platform ===
"KICK")?.username ?? ""; const transformedUsername = username.replaceAll("-",
"_").toLowerCase();) and apply the same defensive normalization in both the
create and delete branches so removeUserStyle/addUserStyle always receive a
string.

Comment on lines +102 to +107
let rgbaColor = typeof shadow.color === "string" ? shadow.color : argbToRgba(shadow.color);
if (!rgbaColor) return null;
rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), ([0-9.]+)\)/, "rgba($1, $2, $3)");
return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
})
.filter(Boolean)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ensure drop-shadow colors remain valid CSS.

The new shadow logic rewrites rgba(r, g, b, a) into rgba(r, g, b), which is invalid syntax and causes CSS parsing failures (the shadow disappears whenever the source had transparency). Please convert to rgb(...) or keep the alpha component intact so we emit a valid color string.

-          rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), ([0-9.]+)\)/, "rgba($1, $2, $3)");
+          rgbaColor = rgbaColor.replace(
+            /rgba\((\d+),\s*(\d+),\s*(\d+),\s*([0-9.]+)\)/,
+            (_match, r, g, b, a) => `rgba(${r}, ${g}, ${b}, ${a})`
+          );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let rgbaColor = typeof shadow.color === "string" ? shadow.color : argbToRgba(shadow.color);
if (!rgbaColor) return null;
rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), ([0-9.]+)\)/, "rgba($1, $2, $3)");
return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
})
.filter(Boolean)
let rgbaColor = typeof shadow.color === "string"
? shadow.color
: argbToRgba(shadow.color);
if (!rgbaColor) return null;
rgbaColor = rgbaColor.replace(
/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([0-9.]+)\)/,
(_match, r, g, b, a) => `rgba(${r}, ${g}, ${b}, ${a})`
);
return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
🤖 Prompt for AI Agents
In src/renderer/src/providers/CosmeticsProvider.jsx around lines 102-107, the
current code strips the alpha from "rgba(r, g, b, a)" producing invalid "rgba(r,
g, b)" CSS; update the logic so emitted drop-shadow colors are valid by handling
the alpha: if alpha is 1 emit "rgb(r, g, b)", otherwise keep the alpha and emit
"rgba(r, g, b, a)"; implement this by parsing the rgba capture groups and
conditionally formatting the replacement string (or simply avoid removing the
alpha entirely) before building the drop-shadow value.

Comment on lines +282 to +285
console.error = window.__KT_ORIGINAL_CONSOLE_ERROR__;
console.warn = window.__KT_ORIGINAL_CONSOLE_WARN__;
console.log('[Console Instrumentation]: Telemetry disabled - restored native console methods');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset console instrumentation state when disabling telemetry.

When telemetry is toggled off, we restore the original console methods but leave window.__KT_CONSOLE_INSTRUMENTED__ truthy. Subsequent re-enables skip re-installing the wrappers, so console telemetry never comes back after the first disable. Please clear the instrumentation flag (and cached originals) when restoring the native handlers.

     if (window.__KT_ORIGINAL_CONSOLE_ERROR__ && console.error !== window.__KT_ORIGINAL_CONSOLE_ERROR__) {
       console.error = window.__KT_ORIGINAL_CONSOLE_ERROR__;
       console.warn = window.__KT_ORIGINAL_CONSOLE_WARN__;
       console.log('[Console Instrumentation]: Telemetry disabled - restored native console methods');
+      delete window.__KT_ORIGINAL_CONSOLE_ERROR__;
+      delete window.__KT_ORIGINAL_CONSOLE_WARN__;
+      delete window.__KT_CONSOLE_INSTRUMENTED__;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error = window.__KT_ORIGINAL_CONSOLE_ERROR__;
console.warn = window.__KT_ORIGINAL_CONSOLE_WARN__;
console.log('[Console Instrumentation]: Telemetry disabled - restored native console methods');
}
if (window.__KT_ORIGINAL_CONSOLE_ERROR__ && console.error !== window.__KT_ORIGINAL_CONSOLE_ERROR__) {
console.error = window.__KT_ORIGINAL_CONSOLE_ERROR__;
console.warn = window.__KT_ORIGINAL_CONSOLE_WARN__;
console.log('[Console Instrumentation]: Telemetry disabled - restored native console methods');
delete window.__KT_ORIGINAL_CONSOLE_ERROR__;
delete window.__KT_ORIGINAL_CONSOLE_WARN__;
delete window.__KT_CONSOLE_INSTRUMENTED__;
}
🤖 Prompt for AI Agents
In src/renderer/src/telemetry/webTracing.js around lines 282 to 285, when
telemetry is disabled the code restores original console methods but leaves
window.__KT_CONSOLE_INSTRUMENTED__ and the cached originals set, causing
re-enables to skip re-installing wrappers; update the disable path to also clear
window.__KT_CONSOLE_INSTRUMENTED__ (set to false or delete) and remove cached
originals (delete window.__KT_ORIGINAL_CONSOLE_ERROR__,
window.__KT_ORIGINAL_CONSOLE_WARN__, and any other __KT_ORIGINAL_CONSOLE_*
entries) so that re-enabling telemetry correctly reinstalls console
instrumentation.

Comment on lines +1306 to +1315
if (process.env.NODE_ENV === 'development') {
try {
const testTracer = trace.getTracer('kicktalk-renderer');
const s = testTracer.startSpan('renderer_export_smoke');
s.end();
if (typeof provider.forceFlush === 'function') {
provider.forceFlush().catch(() => {});
}
} catch {}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid process.env in renderer; use Vite env guard instead.

Accessing process.env.NODE_ENV in the renderer breaks when Node globals are unavailable (our build disables direct Node APIs per renderer guidelines), causing a ReferenceError that short-circuits telemetry initialisation in production. Please gate the dev-only span emission via import.meta.env.DEV (or a similar renderer-safe flag) instead. As per coding guidelines.

-      if (process.env.NODE_ENV === 'development') {
+      if (import.meta.env.DEV) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (process.env.NODE_ENV === 'development') {
try {
const testTracer = trace.getTracer('kicktalk-renderer');
const s = testTracer.startSpan('renderer_export_smoke');
s.end();
if (typeof provider.forceFlush === 'function') {
provider.forceFlush().catch(() => {});
}
} catch {}
}
if (import.meta.env.DEV) {
try {
const testTracer = trace.getTracer('kicktalk-renderer');
const s = testTracer.startSpan('renderer_export_smoke');
s.end();
if (typeof provider.forceFlush === 'function') {
provider.forceFlush().catch(() => {});
}
} catch {}
}
🤖 Prompt for AI Agents
In src/renderer/src/telemetry/webTracing.js around lines 1306 to 1315, the code
uses process.env.NODE_ENV which can throw in the renderer when Node globals are
disabled; replace that check with the renderer-safe Vite guard
(import.meta.env.DEV) so the dev-only tracer smoke span is only emitted when
running in dev. Change the conditional to use import.meta.env.DEV, keep the
try/catch and forceFlush logic identical, and ensure no other references to
process.env are used in this block so telemetry init won't crash in production.

Comment on lines +879 to +893
const stvEmoteSetId = chatroom?.channel7TVEmotes?.[1]?.setInfo?.id || "0";
this.stvWebSocket.addChatroom(chatroom.id, chatroom.streamerData.user_id, stvId, stvEmoteSetId);

// Fetch initial data
await this.fetchInitialMessages(chatroom);
await this.fetchInitialChatroomInfo(chatroom);

// Fetch emotes in background (non-blocking)
this.fetchChatroomEmotes(chatroom).catch(error => {
console.warn(`[ConnectionManager] Background emote fetch failed for ${chatroom.username}:`, error);
});

// Mark as loaded
this.loadedChatrooms.add(chatroomId);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Recover 7TV IDs via the same channel-emote lookup used elsewhere.

Here we fall back to streamerData?.user?.stv_id and channel7TVEmotes?.[1] when wiring the shared 7TV socket. In practice the channel set is not guaranteed to sit at index 1, so we frequently send "0" for both IDs, skipping cosmetics/mention subscriptions for lazily loaded rooms. Please mirror the addChatroom logic (find((set) => set.type === "channel")) so we always pick up the real set/user IDs.

-      const stvId = chatroom.streamerData?.user?.stv_id || "0";
-      const stvEmoteSetId = chatroom?.channel7TVEmotes?.[1]?.setInfo?.id || "0";
+      const channelEmoteSet = Array.isArray(chatroom.channel7TVEmotes)
+        ? chatroom.channel7TVEmotes.find((set) => set.type === "channel")
+        : null;
+      const stvId = channelEmoteSet?.user?.id || "0";
+      const stvEmoteSetId = channelEmoteSet?.setInfo?.id || "0";
       this.stvWebSocket.addChatroom(chatroom.id, chatroom.streamerData.user_id, stvId, stvEmoteSetId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const stvEmoteSetId = chatroom?.channel7TVEmotes?.[1]?.setInfo?.id || "0";
this.stvWebSocket.addChatroom(chatroom.id, chatroom.streamerData.user_id, stvId, stvEmoteSetId);
// Fetch initial data
await this.fetchInitialMessages(chatroom);
await this.fetchInitialChatroomInfo(chatroom);
// Fetch emotes in background (non-blocking)
this.fetchChatroomEmotes(chatroom).catch(error => {
console.warn(`[ConnectionManager] Background emote fetch failed for ${chatroom.username}:`, error);
});
// Mark as loaded
this.loadedChatrooms.add(chatroomId);
// Recover 7TV channel emote set (instead of assuming index 1)
const channelEmoteSet = Array.isArray(chatroom.channel7TVEmotes)
? chatroom.channel7TVEmotes.find((set) => set.type === "channel")
: null;
const stvId = channelEmoteSet?.user?.id || "0";
const stvEmoteSetId = channelEmoteSet?.setInfo?.id || "0";
this.stvWebSocket.addChatroom(
chatroom.id,
chatroom.streamerData.user_id,
stvId,
stvEmoteSetId
);
// Fetch initial data
await this.fetchInitialMessages(chatroom);
await this.fetchInitialChatroomInfo(chatroom);
// Fetch emotes in background (non-blocking)
this.fetchChatroomEmotes(chatroom).catch(error => {
console.warn(
`[ConnectionManager] Background emote fetch failed for ${chatroom.username}:`,
error
);
});
// Mark as loaded
this.loadedChatrooms.add(chatroomId);

Comment on lines +915 to +919
return {
totalDeferredChatrooms: this.deferredChatrooms.length,
loadedChatrooms: this.loadedChatrooms.size,
pendingChatrooms: this.deferredChatrooms.length - this.loadedChatrooms.size
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix pending-count math for deferred chatrooms.

pendingChatrooms = deferred.length - loadedChatrooms.size goes negative once the loaded set contains eagerly-initialized rooms (or when deferred is empty but we’ve already loaded some). This breaks the UI counters for background loads. Please intersect loadedChatrooms with the deferred IDs (and clamp at ≥0) so the status reflects only deferred rooms.

-    return {
-      totalDeferredChatrooms: this.deferredChatrooms.length,
-      loadedChatrooms: this.loadedChatrooms.size,
-      pendingChatrooms: this.deferredChatrooms.length - this.loadedChatrooms.size
-    };
+    const deferredIds = new Set(this.deferredChatrooms.map((room) => room.id));
+    const loadedWithinDeferred = Array.from(this.loadedChatrooms).filter((id) => deferredIds.has(id)).length;
+    const totalDeferred = deferredIds.size;
+    return {
+      totalDeferredChatrooms: totalDeferred,
+      loadedChatrooms: loadedWithinDeferred,
+      pendingChatrooms: Math.max(totalDeferred - loadedWithinDeferred, 0),
+    };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return {
totalDeferredChatrooms: this.deferredChatrooms.length,
loadedChatrooms: this.loadedChatrooms.size,
pendingChatrooms: this.deferredChatrooms.length - this.loadedChatrooms.size
};
// Only count loaded chatrooms that were originally deferred,
// and clamp pending count at zero.
const deferredIds = new Set(this.deferredChatrooms.map((room) => room.id));
const loadedWithinDeferred = Array.from(this.loadedChatrooms)
.filter((id) => deferredIds.has(id)).length;
const totalDeferred = deferredIds.size;
return {
totalDeferredChatrooms: totalDeferred,
loadedChatrooms: loadedWithinDeferred,
pendingChatrooms: Math.max(totalDeferred - loadedWithinDeferred, 0),
};
🤖 Prompt for AI Agents
In utils/services/connectionManager.js around lines 915-919, the
pendingChatrooms math currently subtracts the entire loadedChatrooms.size from
deferredChatrooms.length which can go negative; instead compute how many
deferred IDs are already loaded by intersecting deferredChatrooms with
loadedChatrooms (e.g. count deferred IDs for which loadedChatrooms.has(id)),
subtract that intersection count from deferredChatrooms.length, and clamp the
result with Math.max(0, ...) so pendingChatrooms never becomes negative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant